<?php
/**
 * PHP的简单缓存(只能保存变量,避免序列化)
 *
 * @filesource datafile.class.php
 * @package DsCache
 * @subpackage 
 * @version $id: 1.2, utf8, Sun Dec 13 21:21:01 CST 2009
 * @author LD King <kldscs[at]gmail.com>
 * @copyright Copyleft (D.) 2007 - 2009 MiFunny China Inc.
 * @link http://mifunny.info/
 * @example 
 *  require_once 'DsCache.interface.php';
 *  require_once 'datafile.class.php';
 *  $cache = new datafile( array('cache_dir'=>'/tmp', ... ) );
 *  ... 
 */
class datafile implements DsCache{

	/**
	 * 默认的缓存策略
	 *
	 * DSCACHE_LIFE_TIME - life_time: 缓存有效时间(秒), 默认值 900
     *    如果设置为 0 表示缓存总是存在,
	 *
	 * DSCACHE_ENCODING - encoding_filename: 编码缓存文件名, 默认为 true
     *    默认使用 md5 编码文件名,
     *    设置为 false 则自动去除 ID 中的字符和空格.
     *
     * DSCACHE_DIR - cache_dir: 缓存目录（必须指定）
	 *    $id字段存在带有目录, 则会在缓存目录下创建子目录, 支持多级目录.
	 *    (建议不要在单目录下放置超过1000个文件)
     *
	 * DSCACHE_UMASK - cache_dir_umask: 创建缓存目录的权限, 默认为 0700
     *
	 * @var array
	 */
	private $_default_policy = array(
		'life_time'         => 900,
		'encoding_filename' => true,
		'cache_dir'         => null,
		'cache_dir_umask'   => 0700,
	);
	
	/**
	 * 构造函数
	 *
	 * @param 默认的缓存策略 $default_policy
	 */
	public function __construct(array $default_policy = null){
		if (!is_null($default_policy))
			$this->_default_policy = array_merge($this->_default_policy, $default_policy);
	}

    /**
     * 初始化 默认策略
     *
	 * @param 默认的缓存策略 $default_policy
     */    
	public function _init(array $default_policy = null){
		if (!is_null($default_policy)){
            $this->_default_policy = $this->_policy($default_policy);
        }else{
            throw new RuntimeException("Error: Give me my own policy please! (class:".__CLASS__.")");
        }
	}
	
	/**
     * 写入缓存
     *
     * @param string $id
     * @param mixed $data
     * @param int $expiration
     */
    public function set($id, $data, $expiration = null){
    	$life_time = is_null($expiration) ? $this->_default_policy['life_time'] : $expiration;
        $path = $this->_path($id);
        
        if ($life_time != 0) {
        	$life_time += time();
        }
        $content = array(
            'expired' => $life_time,
            'data'    => $data,
        );
        $content = '<?php return ' . var_export($content, true) . ';';

        file_put_contents($path, $content, LOCK_EX);
        file_put_contents($path, php_strip_whitespace($path), LOCK_EX);
        clearstatcache();
        return true;
    }
	
    /**
     * 读取缓存，失败或缓存撒失效时返回 false
     *   缓存时间为0, 不检查缓存时间
     *
     * @param string $id
     * @param int $expiration
     *
     * @return mixed
     */
    public function get($id, $expiration = null){
        $path = $this->_path($id);
        
        if (!is_file($path)) return false; 

        $data = include($path);
        if (!is_array($data) || !isset($data['expired'])) return false;

        if ($data['expired'] == 0 OR $expiration == 0){
            return $data['data'];
        }else{
            return ($data['expired'] > time()) ? $data['data'] : false;
        }
    }

	/**
	 * 删除指定的缓存
	 *
	 * @param string $id
	 */
	public function remove($id, $time=0){
		$path = $this->_path($id);
		if (is_file($path)) { unlink($path); }
	}
	
	/**
	 * 删除所有的缓存
	 */
	public function clean($delay=0){
    	$this->delete($this->_default_policy['cache_dir'],true);
	}
	
	/**
	 * 清空文件夹
	 * 
	 * @param string $path
	 * @param boolean $is_root
	 */
	public function delete($path,$is_root=false){
		if(is_dir($path)){
			if($is_root) $cycles = true;
			elseif( !@rmdir($path) ) $cycles = true;
			else $cycles = false;
			
			if ( $cycles ) {
				if ( $dp = opendir($path) ) {  
                    while( ($file=readdir($dp)) != false ) {

                        if($file!='.' and $file!='..'){
                            $file = $path.DIRECTORY_SEPARATOR.$file;
                            if ( is_dir($file) ) {
                                $this->delete( $file );
                             } else {
					            unlink( $file );
                             }
                        }//. ..
					}
                    closedir($dp);
                    if( !$is_root ) rmdir($path); 
				}else{
					throw new RuntimeException("Can't open directory: {$path}");
				}
			}//all ready remove dir
		}else unlink($path);
	}
	
    /**
     * 确定缓存文件名
     *
     * @param string $id
     * @param array $policy
     *
     * @return string
     */
    protected function _path($id){
    	$path = pathinfo($id);
    	
    	if ( $this->_default_policy['encoding_filename'] ){
			$filename = 'DsCache_' . md5( $path['basename'] ) . '.php';
		}else{
			$filename = 'DsCache_' . preg_replace("/[[:punct:]]|[[:space:]]/", '', $path['basename']) . '.php';
		}	
		
        $real_dir = $this->_default_policy['cache_dir'];
        $path['dirname'] = trim( $path['dirname'], '\\/');
        if( $path['dirname'] != '.' OR !empty($path['dirname']) ){
            $real_dir .= DIRECTORY_SEPARATOR.$path['dirname'];
            //if( is_dir($real_dir) ) {
            //	$real_dir = rtrim($real_dir, '\\/');
            //}else{
            if( !is_dir($real_dir) ){
            	$real_dir = $this->_default_policy['cache_dir'];
            	 foreach( preg_split('/\/|\\\/', $path['dirname'], -1,PREG_SPLIT_NO_EMPTY) as $d ){
                 	$real_dir .= DIRECTORY_SEPARATOR.$d;
                    if( !is_dir($real_dir) ){
                    	mkdir($real_dir, $this->_default_policy['cache_dir_umask']);
                    }
                 }//END foreach subdir
            }//NOT a dir
		}
		
        return $real_dir . DIRECTORY_SEPARATOR . $filename;
    }

    /**
     * 返回有效的策略选项
     *
     * @param array $policy
     * @return array
     */
    protected function _policy(array $policy = null){
    	$my_policy = is_null($policy) ? $this->_default_policy : array_merge($this->_default_policy, $policy);
    	if(!is_dir($my_policy['cache_dir']) or !is_writable($my_policy['cache_dir'])){
    		throw new RuntimeException("Please Fix Cache Directory({$my_policy['cache_dir']})!");
    	}else{
    		$my_policy['cache_dir'] = rtrim($my_policy['cache_dir'], '\\/');
    	}
        return $my_policy;
    }
}//END class datafile
?>
